/*
    Author: Christopher Diggins
    License: MIT Licence 1.0
    Website: http://www.cdiggins.com
*/

#ifndef LLS_FROM_LUA_HPP
#define LLS_FROM_LUA_HPP

namespace lls
{
	struct LLchemeFromLua
	{
		lua_State* L;

		LLchemeFromLua(LuaMachine& l) 
			: L(l.GetState())
		{ }

		SExpr* FromLuaMachine() {
			lua_getglobal(L, "ast");
			Ast* ast = new Ast();
			AddChildrenFromTable(ast);
			return ast;
		}

		int TopIndex() {
			return -1;
		}

		void Pop() {
			lua_pop(L, 1);
		}

		String PopString() {
			String s = lua_tolstring(L, TopIndex(), NULL);
			Pop();
			return s;
		}

		String GetStringField(ConstString s) {
			int n = GetStackSize();
			Assert(IsTable());
			lua_getfield(L, TopIndex(), s.c_str());
			Assert(IsString());
			String r = PopString();
			int tmp = GetStackSize();
			Assert(tmp == n);
			return r;
		}

		String GetElement() {
			return GetStringField("element");
		}

		Name* GetName() {
			return new Name(GetStringField("name"));
		}

		Name* GetIndex() {
			return new Name(GetStringField("index"));
		}

		Type* GetType() {
			return new Type(GetStringField("type"));
		}

		void GetLuaField(ConstString s) {
			int n = GetStackSize();
			Assert(IsTable());
			lua_getfield(L, TopIndex(), s.c_str());
			int tmp = GetStackSize();
			Assert(tmp == n + 1);
		}

		void GetTableField(ConstString s) {
			int n = GetStackSize();
			Assert(IsTable());
			lua_getfield(L, TopIndex(), s.c_str());
			Assert(IsTable());
			int tmp = GetStackSize();
			Assert(tmp == n + 1);
		}

		bool IsTable() {
			return lua_istable(L, TopIndex()) != 0;
		}

		bool IsString() {
			return lua_isstring(L, TopIndex()) != 0;
		}

		template<typename T>
		SExpr* ArrayFieldToSExpr(ConstString s) {
			int n = GetStackSize();
			GetTableField(s);
			T* r = new T();
			AddChildrenFromTable(r);
			int tmp = GetStackSize();
			Assert(tmp == n);
			return r;
		}

		SExpr* GetFormals() {
			return ArrayFieldToSExpr<Formals>("formals");
		}

		SExpr* GetStatements() {
			return ArrayFieldToSExpr<Seq>("statements");
		}

		SExpr* GetArgs() {
			return ArrayFieldToSExpr<Args>("args");
		}

		SExpr* TableFieldToSExpr(ConstString s) {
			GetTableField(s);
			return MakeSExpr();
		}

		int GetStackSize() {
			return lua_gettop(L);
		}

		void AddChildrenFromTable(SExpr* x) {
			int n = GetStackSize();
			Assert(IsTable());
			lua_pushnil(L);  
			int tmp = GetStackSize();
			Assert(tmp == n + 1);
			while (lua_next(L, -2) != 0) {
				tmp = GetStackSize();
				Assert(tmp == n + 2);
				SExpr* child = MakeSExpr();
				tmp = GetStackSize();
				Assert(tmp == n + 1);
				if (child != NULL) {
					x->AddChild(child);
				}
			}
			Pop(); // collection
			tmp = GetStackSize();
			Assert(tmp == n - 1);
		}

		SExpr* MakeSExpr() {			
			int n = GetStackSize();
			Assert(IsTable());
			String s = GetElement();
			//printf("%s\n", s.c_str());
			SExpr* r = NULL;
			if (s == "module") {
				r = new Module();				
				r->AddChild(GetName());
				r->AddChild(GetStatements());
			}
			else if (s == "func") {
				r = new Define();
				r->AddChild(GetName());
				r->AddChild(GetFormals());
				r->AddChild(GetType());
				r->AddChild(TableFieldToSExpr("body"));
			}
			else if (s == "formal") {
				r = new Formal();
				r->AddChild(GetName());
				r->AddChild(GetType());
			}
			else if (s == "if") {
				r = new If();
				r->AddChild(TableFieldToSExpr("condition"));
				r->AddChild(TableFieldToSExpr("ontrue"));
				r->AddChild(TableFieldToSExpr("onfalse"));
			}
			else if (s == "foreach") {
				r = new ForEach();
				r->AddChild(GetIndex());
				r->AddChild(GetType());
				r->AddChild(TableFieldToSExpr("object"));
				r->AddChild(TableFieldToSExpr("body"));
			}
			else if (s == "call") {
				r = new Apply();
				r->AddChild(TableFieldToSExpr("func"));
				r->AddChild(GetArgs());
			}
			else if (s == "get") {
				r = new Get();
				r->AddChild(GetName());
			}
			else if (s == "literal_int") {
				String val = GetStringField("value");
				r = new PLiteral<int>(StrToInt(val));
			}
			else if (s == "literal_string") {
				String val = GetStringField("value");
				r = new PLiteral<String>(val);
			}
			else if (s == "block") {
				r = GetStatements();
			}
			else if (s == "and") {
				r = new Primitive(prim_and);
				GetLuaField("args");
				AddChildrenFromTable(r);
			}
			else if (s == "eq") {
				r = new Primitive(prim_eq);
				GetLuaField("args");
				AddChildrenFromTable(r);
			}
			else if (s == "or") {
				r = new Primitive(prim_or);
				GetLuaField("args");
				AddChildrenFromTable(r);
			}
			else if (s == "getfield") {
				r = new GetField();
				r->AddChild(TableFieldToSExpr("object"));
				r->AddChild(GetName());
			}
			else if (s == "setfield") {
				r = new SetField();
				r->AddChild(TableFieldToSExpr("object"));
				r->AddChild(GetName());
				r->AddChild(TableFieldToSExpr("value"));
			}
			else {
				Error("unhandled AST element type " + s);
			}
			int tmp = GetStackSize();
			Assert(tmp == n);
			Pop();
			return r;
		}
	};
}

#endif
